var SwipeSlide = function(container, options) {
	this.options = Zepto.extend({
		first : 0, // the first visible slide on initialization
		visibleSlides : 1, // number of slides visible at the same time
		vertical : false, // horizontal or vertical
		tolerance : 0.5, // values between 0 and 1, where 1 means you have to
							// drag to the center of the slide (a value of 1
							// equals the ios behaviour)
		delay : 0.3, // animation speed in seconds,
		easing : 'ease-out', // the easing function
		autoPlay : false, // false, or value in seconds to start auto
							// slideshow
		useTranslate3d : true,
		bulletNavigation : 'link', // will insert bullet navigation: false,
									// true or 'link' (event handlers will be
									// attached)
		directionalNavigation : false, // will insert previous and next links
		beforeChange : null,
		afterChange : null
			// after slide transition callback
		}, options)

	this.isVertical = !!this.options.vertical
	this.container = Zepto(container).addClass('ui-swipeslide')
			.addClass('ui-swipeslide-'
					+ (this.isVertical ? 'vertical' : 'horizontal'))
	this.reel = this.container.children().first()
			.addClass('ui-swipeslide-reel')
	this.slides = this.reel.children().addClass('ui-swipeslide-slide')
	this.numPages = Math.ceil(this.slides.length / this.options.visibleSlides)
	this.currentPage = this.validPage(this.options.first)
	this.touch = {}
	this.isTouch = 'ontouchstart' in document.documentElement
	this.events = {
		start : this.isTouch ? 'touchstart' : 'mousedown',
		move : this.isTouch ? 'touchmove' : 'mousemove',
		end : this.isTouch
				? 'touchend touchcancel touchleave'
				: 'mouseup mouseout mouseleave',
		click : this.isTouch ? 'touchend' : 'click'
	}
	this.setup()
	this.addEventListeners()
	if (this.options.directionalNavigation)
		this.setupDirectionalNavigation()
	if (this.options.bulletNavigation)
		this.setupBulletNavigation()
	if (this.options.autoPlay)
		this.autoPlay()
}

SwipeSlide.prototype = {
	// public
	page : function(index) {
		this.stopAutoPlay()
		var newPage = this.validPage(index), callback
		// only set callback function if a slide happend
		if (this.currentPage != newPage) {
			if (Zepto.isFunction(this.options.beforeChange))
				this.options.beforeChange(this, this.currentPage, newPage)
			this.currentPage = newPage
			callback = Zepto.proxy(this.callback, this)
		} else if (this.options.autoPlay) {
			callback = Zepto.proxy(this.autoPlay, this)
		}
		this.move(0, this.options.delay, callback)
		if (this.options.bulletNavigation)
			this.setActiveBullet()
	},
	first : function() {
		this.page(0)
	},
	next : function() {
		this.page(this.currentPage + 1)
	},
	prev : function() {
		this.page(this.currentPage - 1)
	},
	last : function() {
		this.page(this.numPages - 1)
	},
	isFirst : function() {
		return this.currentPage == 0
	},
	isLast : function() {
		return this.currentPage == this.numPages - 1
	},
	validPage : function(num) {
		return Math.max(Math.min(num, this.numPages - 1), 0)
	},
	autoPlay : function() {
		if (this.timeout)
			return false
		var fn = this.isLast() ? this.first : this.next
		this.timeout = setTimeout(Zepto.proxy(fn, this), this.options.autoPlay
						* 1000)
	},
	stopAutoPlay : function() {
		clearTimeout(this.timeout)
		delete this.timeout
	},
	visibleSlides : function() {
		return this.slides.slice(this.currentPage, this.currentPage
						+ this.options.visibleSlides)
	},

	// private
	move : function(distance, delay, callback) {
		this.reel.animate(this.animationProperties(distance), {
					duration : delay * 1000,
					easing : this.options.easing,
					complete : callback
				})
	},

	animationProperties : function(distance) {
		var position = -this.currentPage * this.dimension + distance + 'px', props = {}
		if (this.options.useTranslate3d) {
			props['translate3d'] = (this.isVertical
					? '0,' + position
					: position + ',0')
					+ ',0'
		} else {
			props[this.isVertical ? 'translateY' : 'translateX'] = position
		}
		return props
	},

	setup : function() {
		var fn = this.isVertical ? 'height' : 'width'
		this.dimension = this.container[fn]()
		this.tolerance = this.options.tolerance * this.dimension / 2
		// set height or width of reel and slides
		this.reel[fn](this.dimension * this.numPages + 'px')
		this.slides[fn](this.dimension / this.options.visibleSlides + 'px')
		// move to first slide without animation
		this.move(0, 0)
	},

	addEventListeners : function() {
		// bind listeners for touch movement
		this.reel.on(this.events.start, Zepto.proxy(this.touchStart, this)).on(
				this.events.move, Zepto.proxy(this.touchMove, this)).on(
				this.events.end, Zepto.proxy(this.touchEnd, this))

		// bind listeners to any elments with '.prev', '.next', '.first' or
		// '.last' class
		this.container.on(this.events.click, '.next',
				Zepto.proxy(this.next, this)).on(this.events.click, '.prev',
				Zepto.proxy(this.prev, this)).on(this.events.click, '.first',
				Zepto.proxy(this.first, this)).on(this.events.click, '.last',
				Zepto.proxy(this.last, this))

		// recalculate dimension on window resize or orientation change
		Zepto(window).on('resize', Zepto.proxy(this.setup, this))
	},

	touchStart : function(e) {
		this.touch.start = this.trackTouch(e)
		delete this.isScroll
		if (!this.isTouch)
			return false
	},

	touchMove : function(e) {
		if (!this.touch.start)
			return
		this.touch.end = this.trackTouch(e)
		var distance = this.distance(this.isVertical)
		if (typeof this.isScroll == 'undefined') {
			this.isScroll = Math.abs(distance) < Math.abs(this
					.distance(!this.isVertical))
		}
		if (!this.isScroll) {
			this.stopAutoPlay()
			this.move(this.withResistance(distance), 0)
			return false
		}
	},

	touchEnd : function(e) {
		if (!this.isScroll) {
			var distance = this.distance(this.isVertical), add = 0
			if (Math.abs(distance) > this.tolerance)
				add = distance < 0 ? 1 : -1
			this.page(this.currentPage + add)
		}
		this.touch = {}
		return true
	},

	trackTouch : function(e) {
		var o = this.isTouch ? e.touches[0] : e
		return {
			x : o.pageX,
			y : o.pageY
		}
	},

	distance : function(vertical) {
		var d = vertical ? 'y' : 'x'
		try {
			return this.touch.end[d] - this.touch.start[d]
		} catch (e) {
			return 0
		}
	},

	withResistance : function(d) {
		if (this.isFirst() && d > 0 || this.isLast() && d < 0)
			d /= (1 + Math.abs(d) / this.dimension)
		return d
	},

	callback : function() {
		// call user defined callback function with the currentPage number and
		// an array of visible slides
		if (Zepto.isFunction(this.options.afterChange))
			this.options.afterChange(this, this.currentPage)
		if (this.options.autoPlay)
			this.autoPlay()
	},

	/* prev/next navigation */
	setupDirectionalNavigation : function() {
		this.container
				.append('<ul class="ui-swipeslide-nav"><li class="prev">Previous</li><li class="next">Next</li></ul>')
	},

	/* bullet navigation */
	setupBulletNavigation : function() {
		this.navBullets = Zepto('<ul class="ui-swipeslide-bullets"></ul>')
		for (i = 0; i < this.numPages; i++)
			this.navBullets.append('<li data-index="' + i + '">' + (i + 1)
					+ '</li>')
		if (this.options.bulletNavigation == 'link') {
			this.navBullets.on(this.events.click, 'li', Zepto.proxy(
							function(e) {
								this.page(parseInt(Zepto(e.currentTarget)
												.data('index'), 10))
							}, this))
		}
		this.container.append(this.navBullets)
		this.setActiveBullet()
	},
	setActiveBullet : function() {
		this.navBullets.children('li').removeClass('active')
				.eq(this.currentPage).addClass('active')
	}
}

var SwipeSlide3D = function(container, options) {
	SwipeSlide.call(this, container, options)
	this.container.addClass('ui-swipeslide-3d')
}
SwipeSlide3D.prototype = new SwipeSlide

Zepto.extend(SwipeSlide3D.prototype, {
			setup : function() {
				var fn = this.isVertical ? 'height' : 'width'
				this.dimension = this.container[fn]()
				this.tolerance = this.options.tolerance * this.dimension / 2
				this.alpha = 360 / this.slides.length
						* (this.isVertical ? -1 : 1)
				this.radius = Math.round((this.dimension / 2)
						/ Math.tan(Math.PI / this.slides.length))
				this.slides.each(Zepto.proxy(this.positionSlide, this))
				this.move(0, 0)
			},
			validPage : function(num) {
				if (num < 0)
					num += this.numPages
				else if (num >= this.numPages)
					num %= this.numPages
				return num
			},
			animationProperties : function(distance) {
				var delta = (this.alpha * distance / this.dimension)
						- (this.alpha * this.currentPage)
				return {
					translate3d : '0,0,' + -this.radius + 'px',
					rotate3d : this.vectorsWithDeg(delta)
				}
			},
			positionSlide : function(i, slide) {
				Zepto(slide).animate({
							rotate3d : this.vectorsWithDeg(i * this.alpha),
							translate3d : '0,0,' + this.radius + 'px'
						}, {
							duration : 0
						})
			},
			vectorsWithDeg : function(degree) {
				return (this.isVertical ? '1,0' : '0,1') + ',0,' + degree
						+ 'deg'
			},
			withResistance : function(d) {
				return d
			} // no resistance for 3d
		})

// zepto plugin
;
(function(Zepto) {
	Zepto.fn.swipeSlide = function(options) {
		var klass = (options = options || {}).threeD
				? SwipeSlide3D
				: SwipeSlide
		return this.each(function() {
					new klass(this, options)
				})
	}
})(window.Zepto)